/**
 * Copyright 2010, 2011 Guenther Hoelzl, Shawn Brown
 *
 * This file is part of MINDdroid.
 *
 * MINDdroid is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * MINDdroid is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * MINDdroid. If not, see <http://www.gnu.org/licenses/>.
 **/

package com.evolved.automata.android.bots;

import android.app.*;
import android.bluetooth.*;
import android.hardware.*;
import android.content.*;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.*;
import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;




import android.media.*;
import android.widget.*;
import android.content.pm.FeatureInfo;
import android.content.res.*;

/**
 * This class is for talking to a LEGO NXT robot and controlling it
 * via bluetooth and the built in acceleration sensor.
 * The communciation to the robot is done via LCP (LEGO communication protocol), 
 * so no special software has to be installed on the robot.
 */
public class BalancingBot extends Activity implements SensorEventListener, BalanceController.MessageReceiver
{

	float prevX=0;
	float prevY=0;
	float prevZ=0;
    /********************************************************
     * Start of Bluetooth and Mindstorms related Globals 
     ********************************************************
     */
	
    public static final int UPDATE_TIME = 200;
    public static final int MENU_TOGGLE_CONNECT = Menu.FIRST;
    public static final int MENU_QUIT = Menu.FIRST + 1;
    
    private static final int REQUEST_CONNECT_DEVICE = 1000;
    private static final int REQUEST_ENABLE_BT = 2000;
    
    private NewBTCommunicator myBTCommunicator = null;
    
    private ProgressDialog connectingProgressDialog;
   
    private Activity thisActivity;
    
    private boolean debug=true;
    private Toast reusableToast;
    
    
    private final int TTS_CHECK_CODE = 9991;
    private Dialog deviceChoserDialog;
    final int DEVICE_CH0OSER_DIALOG=0;
    private String selectedMacAddress=null;
    
    int distance;
    int prevPower=0;
    /********************************************************
     * End of Bluetooth and Mindstorms related Globals 
     ********************************************************
     */
    
    /******************************
     * Start of Balancing related Globals 
     ******************************
     */
    private SensorManager j_SensorManager;
    private Sensor j_Accelerometer;


    private long previousTimeMilli=0;
    private long sampleIntervalMilli=80;
    Lock timeLock=null;
    BalanceController balanceController=null;
    private int counter=0;
    private float xAccellAvg=0;
    private float yAccellAvg=0;
    private float zAccellAvg=0;
    boolean second=false;
    MediaPlayer soundMaker;
    boolean balanceMode=false;
    
    
    /******************************
     * End of Balancing related Globals 
     ******************************
     */
    
    /************************** 
     * Begin of Sound Related Fields
     ************************** 
     */
    Uri OLD_PHONE=Uri.parse("content://media/internal/audio/media/24");
    Uri HAPPY = Uri.parse("content://media/internal/audio/media/25");
    Uri STANDARD = Uri.parse("content://media/internal/audio/media/28");
    private AudioManager audionManager;
    private String pickerResult;
    private final int PICKER=0;
    private Ringtone ringTone;
    private Ringtone fallenRingtone;
    private Ringtone readyRingtone;
    
    private BlockingQueue<Integer> playQueue;
    
    private final int STOP_LOOP=0;
    private final int CONTINUE_PLAYING=1;
    private final int STOP_PLAYING=2;
    private final int CHANGE_TO_FALLEN_TONE=3;
    private final int CHANGE_TO_READY_TONE=4;

    private int initialVolume;

    private int MAX_VOLUME=0;
    private int MAX_EFFECTIVE_VOLUME=0;
    private int MIN_VOLUME=100;
    private float maxVolumeToMaxDiffFraction=.8F;
    private float maxDifference=50F;
    private Thread playThread;
    /************************** 
     * End of Sound Related Fields
     ************************** 
     */
    
    /********************************************************
     * Start of State-Machine related Globals 
     ********************************************************
     */
    
    private enum BALANCE_STATE
    {
    	GETTING_INTO_INITIAL_POSITION,
    	ABOUT_TO_START,
    	BALANCING
    }
    
    BALANCE_STATE currentBalanceState;
    int maxWarningIntervalMilli=5000;
    long warningTimeMilli=0;
    float balanceThresholdDiameter=4F;
    /********************************************************
     * End of State-Machine related Globals 
     ********************************************************
     */
    
    /**
     * Receive messages from the NewBTCommunicator
     */
    final Handler myHandler = new Handler() {
        @Override
        public void handleMessage(Message myMessage) {
            switch (myMessage.getData().getInt("message")) {
                case NewBTCommunicator.DISPLAY_TOAST:
                    showToast(myMessage.getData().getString("toastText"), Toast.LENGTH_SHORT);
                    break;
                case NewBTCommunicator.STATE_CONNECTED:
                	myBTCommunicator.setSensorConfiguration();
                	Button startButton = (Button)findViewById(R.id.restartAction);
                	startButton.setEnabled(true);
                    break;
                case NewBTCommunicator.MOTOR_STATE:

                    if (myBTCommunicator != null) 
                    {
                        byte[] motorMessage = myBTCommunicator.getReturnMessage();
                        int position = byteToInt(motorMessage[21]) + (byteToInt(motorMessage[22]) << 8) + (byteToInt(motorMessage[23]) << 16)
                                       + (byteToInt(motorMessage[24]) << 24);
                        // TODO: Do something else here
                        showToast(getResources().getString(R.string.current_position) + position, Toast.LENGTH_SHORT);
                    }

                    break;
                case NewBTCommunicator.STATE_CONNECTERROR:
                    //connectingProgressDialog.dismiss();
                case NewBTCommunicator.STATE_RECEIVEERROR:
                case NewBTCommunicator.STATE_SENDERROR:
                	showToast("An error has occurred", Toast.LENGTH_SHORT);
                	AlertDialog.Builder alertBuilder = new AlertDialog.Builder(thisActivity);
                	alertBuilder.setMessage("There was an error with the connection to the remote device");
                	alertBuilder.setPositiveButton("Connect again", getBTReconnectRetryListener());
                	alertBuilder.setNeutralButton("Choose NXT again", getSelectNXTListener());
                	alertBuilder.setNegativeButton("Quit", getSelectExitListener());
                	alertBuilder.setTitle("Connection Error!");
                	alertBuilder.show();
                    break;
                
            }
        }
    };

    
    private DialogInterface.OnClickListener getBTReconnectRetryListener()
    {
    	return new DialogInterface.OnClickListener()
    	{
    		@Override
			public void onClick(DialogInterface dialog, int which) 
			{
    			myBTCommunicator.onStart(selectedMacAddress);
				dialog.dismiss();
			}
    	};
    }
    
    private DialogInterface.OnClickListener getSelectNXTListener()
    {
    	return new DialogInterface.OnClickListener()
    	{
    		@Override
			public void onClick(DialogInterface dialog, int which) 
			{
    			selectNXT();
				dialog.dismiss();
			}
    	};
    }
    
    private DialogInterface.OnClickListener getSelectExitListener()
    {
    	return new DialogInterface.OnClickListener()
    	{
    		@Override
			public void onClick(DialogInterface dialog, int which) 
			{
    			finish();
				dialog.dismiss();
			}
    	};
    }
    
    public BalancingBot()
    {
    	super();
    	previousTimeMilli = System.currentTimeMillis();
    	balanceController = new BalanceController(this,0F,"/sdcard/data/");
    	
    	
    }
    

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        myBTCommunicator = new NewBTCommunicator(myHandler, getResources());
        thisActivity = this;
        j_SensorManager =  (SensorManager)getSystemService(SENSOR_SERVICE);
		j_Accelerometer = j_SensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		
        
        timeLock = new ReentrantLock();
        
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        reusableToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
        setContentView(R.layout.communicator);
        
        Button exitButton, startButton;
        exitButton = (Button)findViewById(R.id.exitAction);
        startButton = (Button)findViewById(R.id.restartAction);
        startButton.setEnabled(false);
        exitButton.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				//destroyBTCommunicator();
				
				thisActivity.finish();
			}
		});
        
        startButton.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				//executeActionSequence();
				
		    	distance = myBTCommunicator.getDistanceValue();
		    	TextView stateView = (TextView)findViewById(R.id.sonicView);
		    	stateView.setText(""+distance);
			}
		});
        TextView sonic = (TextView)findViewById(R.id.sonicView);
        sonic.setText("No data");
        onCreateSound();
        
    }

	private void onCreateSound()
	{
		playQueue = new ArrayBlockingQueue<Integer>(5);
		Context appContext = getApplicationContext();
		audionManager = (AudioManager)appContext.getSystemService(Context.AUDIO_SERVICE);
		initialVolume = audionManager.getStreamVolume(AudioManager.STREAM_RING);

		MAX_VOLUME=audionManager.getStreamMaxVolume(AudioManager.STREAM_RING);
		MAX_EFFECTIVE_VOLUME=(int)(maxVolumeToMaxDiffFraction*MAX_VOLUME);
	}
	
    // 2
    @Override
    protected void onStart() 
    {
        super.onStart();
        
        onStartSound();
        selectNXT();
        currentBalanceState=BALANCE_STATE.GETTING_INTO_INITIAL_POSITION;
        balanceController.resetAllLearnedDataFiles();
        balanceController.loadAllLearnedDataFiles();
    }
    

    private void onStartSound()
    {
    	if ((playThread!=null)&&(playThread.isAlive()))
    	{
    		playThread.interrupt();
    		playQueue = new ArrayBlockingQueue<Integer>(5);
    	}
    	fallenRingtone = RingtoneManager.getRingtone(getApplicationContext(), OLD_PHONE);
    	readyRingtone = RingtoneManager.getRingtone(getApplicationContext(), HAPPY);
    	ringTone=fallenRingtone;
		startSound();
    }
    
	private void startSound()
	{
		Runnable player = new Runnable(){
			private int last_action=CONTINUE_PLAYING;
			public void run()
			{
				int message;
				try
				{
					
					while (ringTone!=null)
					{
						message=playQueue.take().intValue();
						if (STOP_LOOP==message)
							break;
						last_action=message;
						switch (message)
						{
							case CONTINUE_PLAYING:
								if (!ringTone.isPlaying())
								{
									ringTone.play();
								}
								playQueue.put(CONTINUE_PLAYING);
								break;
							case STOP_PLAYING:
								if (ringTone.isPlaying())
									ringTone.stop();
								playQueue.clear();
								break;
							case CHANGE_TO_FALLEN_TONE:
								if (ringTone!=fallenRingtone)
								{
									ringTone.stop();
									ringTone=fallenRingtone;
								}
								playQueue.put(CONTINUE_PLAYING);
								break;
							case CHANGE_TO_READY_TONE:
								if (ringTone!=readyRingtone)
								{
									ringTone.stop();
									ringTone=readyRingtone;
								}
								playQueue.put(CONTINUE_PLAYING);
								break;
						}
						Thread.sleep(300);
						
					}
				}
				catch (InterruptedException i)
				{
					
				}

		    	if ((ringTone!=null)&&(ringTone.isPlaying()))
		    	{
		    		ringTone.stop();
		    		audionManager.setStreamVolume(AudioManager.STREAM_RING, initialVolume,0);
		    	}
				
			}
			
		};
		playThread = new Thread(player);
		playThread.start();
		
	}
	
	public void playFallTone()
	{
		try
		{
			//playQueue.put(CHANGE_TO_FALLEN_TONE);
		}
		catch (Exception e)
		{
			
		}
	}
	
	public void playReadyTone()
	{
		try
		{
			//playQueue.put(CHANGE_TO_READY_TONE);
		}
		catch (Exception e)
		{
			
		}
	}
	
	public void stopTone()
	{
		try
		{
			//playQueue.put(STOP_PLAYING);
		}
		catch (Exception e)
		{
			
		}
	}
    
    
    @Override
    protected Dialog onCreateDialog(int id)
    {
    	switch (id)
    	{
    		case DEVICE_CH0OSER_DIALOG:
    			Dialog dialog = new Dialog(this);
    			dialog.setContentView(R.layout.device_list);
    			return dialog;
    	}
    	return null;
    }
    
    @Override
    protected void onPrepareDialog(int id, Dialog theDialog, Bundle data)
    {
    	switch (id)
    	{
    		case DEVICE_CH0OSER_DIALOG:
    			ListView pairedDeviceList = (ListView)theDialog.findViewById(R.id.paired_devices);
    			BluetoothAdapter  adapter = BluetoothAdapter.getDefaultAdapter();
    	        
    	        Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();
    			ArrayAdapter<String> deviceList = new ArrayAdapter<String>(this, R.layout.device_name);
    			for (BluetoothDevice device:pairedDevices)
    			{
    				deviceList.add(device.getName()+"-"+device.getAddress());
    			}
    			pairedDeviceList.setAdapter(deviceList);
    			pairedDeviceList.setOnItemClickListener(getOnBluetoothDeviceSelected());
    	}
    }
    
    private AdapterView.OnItemClickListener getOnBluetoothDeviceSelected()
    {
    	return new AdapterView.OnItemClickListener()
    	{
    		public void onItemClick(AdapterView av, View v, int arg2, long arg3) 
            {

                String info = ((TextView) v).getText().toString();
                String[] name_mac_pair = info.split("\\-");
                selectedMacAddress=name_mac_pair[1];
                myBTCommunicator.onStart(selectedMacAddress);
                dismissDialog(DEVICE_CH0OSER_DIALOG);
            }
    	};
    	
    }
    
    @Override
    public void onResume() {
        super.onResume();
        j_SensorManager.registerListener(this, j_Accelerometer, SensorManager.SENSOR_DELAY_FASTEST);
     
    }
    
	@Override
	public void onSensorChanged(SensorEvent event) 
	{
	
		float bxAccel = event.values[0];
		float byAccel = event.values[1];
		float bzAccel = event.values[2];
		
		float xAccel = (bxAccel+prevX)/2;
		float yAccel = (byAccel+prevY)/2;
		float zAccel = (bzAccel+prevZ)/2;
		
		TextView angleView = (TextView)findViewById(R.id.currentAngleView);
		TextView stateView = (TextView)findViewById(R.id.sonicView);
		TextView powerView = (TextView)findViewById(R.id.setAngleView);
		float cangle;
		float sangle;
		cangle = BalanceController.getAngleDeg(xAccel,yAccel, zAccel);
		prevX=bxAccel;
		prevY=byAccel;
		prevZ=bzAccel;
		
		int responsePower=0;
		if (myBTCommunicator.isConnected())
		{	
			responsePower = balanceController.updatePosition(xAccel, yAccel, zAccel);
		}
		if (responsePower!=0)
			prevPower=responsePower;
		angleView.setText((new Float(cangle)).toString());
		//powerView.setText((new Float(balanceController.getCurrentVelocity())).toString());
		//powerView.setText((new Integer(prevPower)).toString());
		//stateView.setText(balanceController.getCurrentState());

		
//		if (!debug&&myBTCommunicator.isConnected())
//		{
//			int responsePower = balanceController.updatePosition(xAccel, yAccel, zAccel);
//			
//			switch (responsePower)
//			{
//				case BalanceController.p_ExitFlagPower:
//				case 0:
//					break;
//				default:
//					int impulseLength = BalanceController.p_ImpulseDurationMilli;
//					myBTCommunicator.updateMotorControl(responsePower, responsePower);
//					myBTCommunicator.sendSleepMessage(impulseLength);
//					myBTCommunicator.updateMotorControl(0, 0);
//			}
//
//		}
//		else
//		{
//			
//			tView.setText((new Float(cangle)).toString());
//		}
		
	}
	
    private void executeActionSequence()
    {
    	
    	for (int i=0;i<20;i++)
    	{
    		myBTCommunicator.updateMotorControl(50, 50);
        	myBTCommunicator.sendSleepMessage(60);
        	myBTCommunicator.updateMotorControl(-50, -50);
        	myBTCommunicator.sendSleepMessage(60);
        	
    	}
    	myBTCommunicator.updateMotorControl(0, 0);
    	
    	TextView sonic = (TextView)findViewById(R.id.sonicView);
    	myBTCommunicator.setUltraSonicDistance();
    	distance = myBTCommunicator.getDistanceValue();
        sonic.setText(String.format("%1$s", distance));
        second=true;
    }
    
    
	
    @Override
    public void onPause() 
    {
    	j_SensorManager.unregisterListener(this);
        super.onPause();
    }
    

    @Override
    public void onStop()
    {
    	onStopSound();
    	myBTCommunicator.onStop();
    	balanceController.saveData();
    	super.onStop();
    }
    
    @Override
    protected void onDestroy() 
    {
        super.onDestroy();
    }





    
    private void onStopSound()
    {
    	if ((playThread!=null)&&(playThread.isAlive()))
    	{
    		try
    		{
    			playQueue.put(new Integer(STOP_PLAYING));
    			playQueue.put(new Integer(STOP_LOOP));
    		}
    		catch (InterruptedException e)
    		{
    			
    		}
    	}
    	if ((ringTone!=null)&&(ringTone.isPlaying()))
    	{
    		ringTone.stop();
    		
    	}
    	audionManager.setStreamVolume(AudioManager.STREAM_RING, initialVolume,0);
    }
        

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
		
	}
	
    /**
     * Displays a message as a toast
     * @param textToShow the message
     * @param length the length of the toast to display
     */
    private void showToast(String textToShow, int length) {
        reusableToast.setText(textToShow);
        reusableToast.setDuration(length);
        reusableToast.show();
    }

    /**
     * Displays a message as a toast
     * @param resID the ressource ID to display
     * @param length the length of the toast to display
     */
    private void showToast(int resID, int length) {
        reusableToast.setText(resID);
        reusableToast.setDuration(length);
        reusableToast.show();
    }
    


    private int byteToInt(byte byteValue) {
        int intValue = (byteValue & (byte) 0x7f);

        if ((byteValue & (byte) 0x80) != 0)
            intValue |= 0x80;

        return intValue;
    }

    // 2.1
    // 
    void selectNXT() 
    {
        showDialog(DEVICE_CH0OSER_DIALOG);
    }



    
    
    /**
     * Initialization of the motor commands for the different robot types.
     */
    // 1.1




	


}
